package com.eks.linux.service;

import com.eksframework.commons.json.EksJson;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;

/**
 * LinuxCommandService
 *
 * @author: chenfenghai
 * @version: 1.0
 * @date: 2021-02-04 15:52
 */
@Slf4j
@Service
public class LinuxCommandServiceImpl implements InitializingBean, CommandService {

    @Value("${cmd.threadname:cmd-executor}")
    private String threadName;

    @Value("${cmd.taskQueueMaxStorage:20}")
    private Integer taskQueueMaxStorage;

    @Value("${cmd.corePoolSize:4}")
    private Integer corePoolSize;

    @Value("${cmd.maximumPoolSize:8}")
    private Integer maximumPoolSize;

    @Value("${cmd.keepAliveSeconds:15}")
    private Integer keepAliveSeconds;
    private ThreadPoolExecutor executor;
    private static final String BASH = "sh";
    private static final String BASH_PARAM = "-c";

    /**
     * Invoked by the containing {@code BeanFactory} after it has set all bean properties
     * and satisfied {@link BeanFactoryAware}, {@code ApplicationContextAware} etc.
     * <p>This method allows the bean instance to perform validation of its overall
     * configuration and final initialization when all bean properties have been set.
     *
     * @throws Exception in the event of misconfiguration (such as failure to set an
     *                   essential property) or if initialization fails for any other reason
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        executor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveSeconds,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(taskQueueMaxStorage),
                target -> new Thread(target, threadName + target.hashCode()),
                new ThreadPoolExecutor.AbortPolicy());
    }

    /**
     * 执行命令并返回结果
     *
     * @param cmd 命令字符串
     * @return 结果
     */
    @Override
    public List<String> execute(String cmd) {
        Process process = null;
        List<String> outList;
        try {
            // need to pass command as bash's param,
            // so that we can compatible with commands: "echo a >> b.txt" or "bash a && bash b"
            ProcessBuilder processBuilder;
            if (StringUtils.startsWith(
                    StringUtils.trimToEmpty(SystemUtils.OS_NAME).toLowerCase(), "windows")) {
                log.info("exec command: {}", cmd);
                processBuilder = new ProcessBuilder(StringUtils.split(cmd, ";"));
            } else {
                List<String> command = new ArrayList<>();
                command.add(BASH);
                command.add(BASH_PARAM);
                command.addAll(Arrays.asList(StringUtils.split(cmd, ";")));
                log.info("exec command: {}", EksJson.toJson(command));
                processBuilder = new ProcessBuilder(command);
            }
            process = processBuilder.start();
            Future<List<String>> errorFuture = executor.submit(new ReadTask(process.getErrorStream()));
            Future<List<String>> outFuture = executor.submit(new ReadTask(process.getInputStream()));
            int exitValue = process.waitFor();
            if (exitValue > 0) {
                throw new RuntimeException(StringUtils.join(errorFuture.get(), ","));
            }
            outList = outFuture.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (process != null) {
                process.destroy();
            }
        }
        return outList;
    }
}

class ReadTask implements Callable<List<String>> {
    private InputStream is;

    ReadTask(InputStream is) {
        this.is = is;
    }

    @Override
    public List<String> call() throws Exception {
        List<String> lineList = new ArrayList<>();
        BufferedReader br = new BufferedReader(new InputStreamReader(is, "GBK"));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) {
            lineList.add(StringUtils.trimToEmpty(line));
        }
        return lineList;
    }
}
